package com.fowo.api.sys.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fowo.api.common.annotaion.Sensitive;
import com.fowo.api.common.model.ImportRow;
import com.fowo.api.sys.annotation.DataActivity;
import com.fowo.api.common.annotaion.TitleField;
import com.fowo.api.sys.component.SysDataActivityAopHelper;
import com.fowo.api.sys.entity.SysDataActivity;
import com.fowo.api.sys.entity.enums.SysDataActivityAction;
import com.fowo.api.sys.mapper.SysDataActivityMapper;
import com.fowo.api.sys.mapper.SysUserMapper;
import com.fowo.api.sys.model.SysDataActivityItemVo;
import com.fowo.api.sys.model.SysDataActivityParam;
import com.fowo.api.sys.model.SysDataActivitySearch;
import com.fowo.api.sys.service.SysDataActivityService;
import com.fowo.api.sys.service.SysParamService;
import com.fowo.api.user.model.JwtUserInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;

@Slf4j
@Service
public class SysDataActivityServiceImpl extends ServiceImpl<SysDataActivityMapper, SysDataActivity> implements SysDataActivityService {

    // 包括生成修改信息时忽略的字段名
    private static final String[] IGNORE_FIELDS = new String[] {
            "createTime",
            "createUser",
            "lastUpdateTime",
            "lastUpdateUser",
            "delFlag"
    };

    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private SysUserMapper sysUserMapper;
    @Resource
    private SysParamService sysParamService;

    @Override
    public void create(SysDataActivity sysDataActivity) {
        save(sysDataActivity);
    }

    @Async
    @Override
    public void createAsync(SysDataActivity sysDataActivity) {
        create(sysDataActivity);
    }

    @DataActivity(action = SysDataActivityAction.Del, message = "%s清空了操作日志")
    @Override
    public boolean clean() {
        baseMapper.clean();
        return true;
    }

    @Async
    @Override
    public void aopHandleAsync(SysDataActivityAopHelper.DataActivityAopContext context, DataActivity dataActivity, ServiceImpl<?, ?> serviceImpl, JwtUserInfo jwtUserInfo, Long id, Object oldObject, Object returnObject) {
        try {
            if (dataActivity.customHandler().length > 0) {
                // 自定义数据活动处理
                for(Class<? extends SysDataActivityService.CustomHandler> customHandlerClass : dataActivity.customHandler()) {
                    SysDataActivityService.CustomHandler customHandler = applicationContext.getBean(customHandlerClass);
                    if (customHandler.handle(this, dataActivity, serviceImpl, jwtUserInfo, id, oldObject, returnObject)) {
                        return;
                    }
                }
            }

            Class<?> entityClass = dataActivity.entityClass().length > 0 ? dataActivity.entityClass()[0] : (serviceImpl != null ? serviceImpl.getEntityClass() : null);
            if (entityClass == null) {
                throw new Exception("无法识别实体类！");
            }
            String operatorUserDisplayName = sysUserMapper.getNameOrUsernameByUserId(jwtUserInfo.getUserId());
            BaseMapper<?> baseMapper = solveBaseMapper(dataActivity, serviceImpl);
            String objectDisplayName = solveObjectDisplayName(dataActivity, entityClass);
            if (dataActivity.action() == SysDataActivityAction.View) {
                String title = solveTitle(oldObject);
                SysDataActivity sysDataActivity = SysDataActivity.builder()
                        .action(dataActivity.action())
                        .dataSource(entityClass.getName())
                        .dataId(id)
                        .data(oldObject)
                        .userId(jwtUserInfo.getUserId())
                        .message(String.format(
                                com.fowo.api.common.util.StringUtils.firstHasText(dataActivity.message(), "%s查看了%s:%s"),
                                operatorUserDisplayName,
                                objectDisplayName,
                                title
                        ))
                        .eventTime(new Date())
                        .build();
                create(sysDataActivity);
            } else if (dataActivity.action() == SysDataActivityAction.Add) {
                if (returnObject == null) {
                    throw new Exception("必须返回新对象 ID 才能生成数据活动记录！");
                }
                Long newId = Long.class.isAssignableFrom(returnObject.getClass()) ? (Long)returnObject : getId(returnObject);
                Object newObj = baseMapper.selectById(newId);
                if (newObj != null) {
                    String title = solveTitle(newObj);
                    SysDataActivity sysDataActivity = SysDataActivity.builder()
                            .action(dataActivity.action())
                            .dataSource(entityClass.getName())
                            .dataId(newId)
                            .data(newObj)
                            .userId(jwtUserInfo.getUserId())
                            .message(String.format(
                                com.fowo.api.common.util.StringUtils.firstHasText(dataActivity.message(), "%s新增了%s：%s"),
                                    operatorUserDisplayName,
                                    objectDisplayName,
                                    title
                            ))
                            .eventTime(new Date())
                            .build();
                    create(sysDataActivity);
                }
            } else if (dataActivity.action() == SysDataActivityAction.Del) {
                String title = solveTitle(oldObject);
                SysDataActivity sysDataActivity = SysDataActivity.builder()
                        .action(dataActivity.action())
                        .dataSource(entityClass.getName())
                        .dataId(id)
                        .data(oldObject)
                        .userId(jwtUserInfo.getUserId())
                        .message(String.format(
                            com.fowo.api.common.util.StringUtils.firstHasText(dataActivity.message(), "%s删除了%s：%s"),
                                operatorUserDisplayName,
                                objectDisplayName,
                                title
                        ))
                        .eventTime(new Date())
                        .build();
                create(sysDataActivity);
            } else if (dataActivity.action() == SysDataActivityAction.Modified) {
                // 相差比较
                Object newObj = baseMapper.selectById(id);
                List<PropertyChangeItem> changeItems = new ArrayList<>();
                ReflectionUtils.doWithFields(newObj.getClass(), f -> {
                    f.setAccessible(true);
                    Object oldVal = f.get(oldObject);
                    Object newVal = f.get(newObj);
                    if (!Objects.equals(oldVal, newVal)) {
                        if (oldVal instanceof CharSequence && newVal instanceof CharSequence) {
                            if (!StringUtils.hasText((CharSequence)oldVal) && !StringUtils.hasText((CharSequence)newVal)) {
                                // null 或 空字符串都是空值
                                return;
                            }
                        }
                        PropertyChangeItem pci = new PropertyChangeItem();
                        pci.oldValue = oldVal;
                        pci.newValue = newVal;
                        pci.field = f;
                        changeItems.add(pci);
                    }
                }, f -> Arrays.stream(IGNORE_FIELDS).noneMatch(fn -> fn.equals(f.getName())));
                if (changeItems.isEmpty()) {
                    return;
                }
                String title = solveTitle(oldObject);
                SysDataActivity sysDataActivity = SysDataActivity.builder()
                        .action(dataActivity.action())
                        .dataSource(entityClass.getName())
                        .dataId(id)
                        .data(changesToJSONObject(changeItems))
                        .userId(jwtUserInfo.getUserId())
                        .message(String.format(
                            com.fowo.api.common.util.StringUtils.firstHasText(dataActivity.message(), "%s修改了%s：%s"),
                                operatorUserDisplayName,
                                objectDisplayName,
                                title
                        ))
                        .eventTime(new Date())
                        .build();
                // 连接所有变更信息
                StringBuilder sb = new StringBuilder();
                for(PropertyChangeItem changeItem : changeItems) {
                    boolean isSensitive = changeItem.field.isAnnotationPresent(Sensitive.class);
                    Schema fieldSchema = changeItem.field.getAnnotation(Schema.class);
                    String fieldLabel = fieldSchema != null ? fieldSchema.title() : changeItem.field.getName();
                    if (sb.length() > 0) {
                        sb.append(",");
                    }
                    if (isSensitive) {
                        sb.append(String.format("%s更新", fieldLabel));
                    } else {
                        sb.append(fieldLabel);
                        if (changeItem.oldValue != null) {
                            sb.append(String.format("“%s”", changeItem.oldValue.toString()));
                        }
                        if (changeItem.newValue != null) {
                            sb.append(String.format("更新为“%s”", changeItem.newValue.toString()));
                        }
                    }
                }
                sb.insert(0, String.format("%s修改%s", operatorUserDisplayName, title));
                sysDataActivity.setMessage(com.fowo.api.common.util.StringUtils.ellipsis(sb.toString(), 254));
                create(sysDataActivity);
            } else if (dataActivity.action() == SysDataActivityAction.Follow) {
                create(SysDataActivity.builder()
                        .action(dataActivity.action())
                        .dataSource(entityClass.getName())
                        .dataId(id)
                        .userId(jwtUserInfo.getUserId())
                        .message(String.format(com.fowo.api.common.util.StringUtils.firstHasText(dataActivity.message(), "%s关注了%s"), operatorUserDisplayName, solveTitle(oldObject)))
                        .eventTime(new Date())
                        .build());
            } else if (dataActivity.action() == SysDataActivityAction.Unfollow) {
                create(SysDataActivity.builder()
                        .action(dataActivity.action())
                        .dataSource(entityClass.getName())
                        .dataId(id)
                        .userId(jwtUserInfo.getUserId())
                        .message(String.format(com.fowo.api.common.util.StringUtils.firstHasText(dataActivity.message(), "%s取消了对%s的关注"), operatorUserDisplayName, solveTitle(oldObject)))
                        .eventTime(new Date())
                        .build());
            } else if (dataActivity.action() == SysDataActivityAction.Import) {
                if (context != null && !context.getIdList().isEmpty()) {
                    for (Long rowId : context.getIdList()) {
                        Object rowObj = serviceImpl.getBaseMapper().selectById(rowId);
                        if (rowObj != null) {
                            create(SysDataActivity.builder()
                                    .action(dataActivity.action())
                                    .dataSource(entityClass.getName())
                                    .dataId(rowId)
                                    .userId(jwtUserInfo.getUserId())
                                    .message(String.format(com.fowo.api.common.util.StringUtils.firstHasText(dataActivity.message(), "%s导入了%s"), operatorUserDisplayName, solveTitle(rowObj)))
                                    .eventTime(new Date())
                                    .build());
                        }
                    }
                }
            } else if(StringUtils.hasText(dataActivity.message())) {
                create(SysDataActivity.builder()
                        .action(dataActivity.action())
                        .dataSource(entityClass.getName())
                        .dataId(id)
                        .userId(jwtUserInfo.getUserId())
                        .message(String.format(dataActivity.message(), operatorUserDisplayName, solveTitle(oldObject)))
                        .eventTime(new Date())
                        .build());
            }
            else {
                log.warn("行为类型“" + dataActivity.action() + "”不支持使用 @DataActivity 自动生成数据活动记录！");
//                throw new Exception("行为类型“" + dataActivity.action() + "”不支持使用 @DataActivity 自动生成数据活动记录！");
            }
        }
        catch (Throwable throwable) {
            log.error("DataActivity 切面处理错误", throwable);
        }
    }

    private Long getId(Object returnObject) throws IllegalAccessException {
        Field idField = ReflectionUtils.findField(returnObject.getClass(), "id", Long.class);
        if (idField == null) {
            return null;
        }
        idField.setAccessible(true);
        return (Long) idField.get(returnObject);
    }


    private String solveTitle(Object oldObject) throws Exception {
        if (oldObject == null) {
            return null;
        }
        List<Field> titleFields = new ArrayList<>();
        ReflectionUtils.doWithFields(oldObject.getClass(), f -> {
            if(f.isAnnotationPresent(TitleField.class)) {
                titleFields.add(f);
            }
        });
        if (titleFields.isEmpty()) {
            // 回退到使用 id
            return String.format("%d", getId(oldObject));
        }
        titleFields.sort(Comparator.comparingInt(a -> a.getAnnotation(TitleField.class).order()));
        for(Field titleField : titleFields) {
            titleField.setAccessible(true);
            Object title = titleField.get(oldObject);
            if (title != null) {
                return (String) title;
            }
        }
        return null;
    }

    private String solveObjectDisplayName(DataActivity dataActivity, Class<?> entityClass) {
        if(StringUtils.hasText(dataActivity.displayName())) {
            return dataActivity.displayName();
        }
        Schema[] annotationsByType = entityClass.getAnnotationsByType(Schema.class);
        if (annotationsByType.length > 0) {
            return annotationsByType[0].title();
        }
        return entityClass.getSimpleName();
    }

    private BaseMapper<?> solveBaseMapper(DataActivity dataActivity, ServiceImpl<?, ?> serviceImpl) throws Exception {
        if (serviceImpl != null && (dataActivity.mapperClass().length == 0 || dataActivity.mapperClass()[0].isAssignableFrom(serviceImpl.getBaseMapper().getClass()))) {
            return serviceImpl.getBaseMapper();
        }
        if (dataActivity.mapperClass().length == 0) {
            throw new Exception("未找到 Mapper Class");
        }
        return applicationContext.getBean(dataActivity.mapperClass()[0]);
    }

    @Override
    public List<SysDataActivityItemVo> search(SysDataActivitySearch search) {
        search.initSort("sda.id desc", "sda.");
        return baseMapper.search(search);
    }

    @Override
    public Page<SysDataActivityItemVo> searchPage(SysDataActivitySearch search) {
        search.initSort("sda.id desc", "sda.");
        return baseMapper.searchPage(search.toPage(), search);
    }

    @Cacheable("SysDataActivityParam")
    @Override
    public SysDataActivityParam getParam() {
        SysDataActivityParam param = sysParamService.getObjectByName("SysDataActivityParam", SysDataActivityParam.class);
        if (param == null) {
            param = new SysDataActivityParam();
        }
        return param;
    }

    @CacheEvict("SysDataActivityParam")
    @Override
    public boolean updateParam(SysDataActivityParam param) throws Exception {
        return sysParamService.setObjectByName("SysDataActivityParam", param);
    }

    @Override
    public boolean isDisable(String dataSource, SysDataActivityAction action) {
        SysDataActivityParam param = getParam();
        if (param == null || param.getDisables() == null) {
            return false;
        }
        String name = String.format("%s:%s", dataSource, action.getValue());
        return param.getDisables().contains(name);
    }

    @Data
    public static class PropertyChangeItem {
        private Field field;
        private Object oldValue;
        private Object newValue;
    }

    /**
     * 将属性变更列表转换为一个 JSON 对象，它包括由 origin 属性对象和 changes 属性对象组成的信息
     */
    static JSONObject changesToJSONObject(List<PropertyChangeItem> changeItems) {
        JSONObject json = new JSONObject();
        JSONObject origin = new JSONObject();
        JSONObject changes = new JSONObject();
        for(PropertyChangeItem pci : changeItems) {
            origin.put(pci.field.getName(), pci.oldValue);
            changes.put(pci.field.getName(), pci.newValue);
        }
        json.put("origin", origin);
        json.put("changes", changes);
        return json;
    }
}
